Este é um conjunto de dados público de comércio eletrônico brasileiro de pedidos feitos na Olist Store .
Em resumo o conjunto de dados contém informações sobre pedidos, produtos adquiridos, review e comentários de clientes.
Esse conjunto de dados foi fornecido pela Olist, a maior loja de departamentos dos mercados brasileiros. O Olist conecta pequenas empresas de todo o Brasil a canais sem problemas e com um único contrato. Esses comerciantes podem vender seus produtos através da Olist Store e enviá-los diretamente aos clientes usando os parceiros de logística da Olist.
Depois que um cliente compra o produto na Olist Store, um vendedor é notificado para atender a esse pedido. Depois que o cliente recebe o produto ou a data estimada de entrega é vencida, ele recebe uma pesquisa de satisfação por e-mail, onde pode anotar a experiência da compra e anotar alguns comentários.
AtençãoOs dados são divididos em vários conjuntos de dados para melhor compreensão e organização. Consulte o seguinte esquema de dados ao trabalhar com ele:
A seguir a base de dados será descrita com o objetivo de procurar insights relevantes; Para realizar as análises será necessário unir as informações presentes nas planilhas correspondentes.Estas informações podem ser combinados utilizando os IDs como mostra o esquema acima.
import pandas as pd
import pyodbc
import sys
print('Python: ' + sys.version.split('|')[0])
## Python: 3.7.6 (default, Jan 8 2020, 20:23:39) [MSC v.1916 64 bit (AMD64)]
print('Pandas: ' + pd.__version__)
## Pandas: 1.0.4
print('pyODBC: ' + pyodbc.version)
## pyODBC: 4.0.0-unsupported
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns
import datetime
import scipy.stats as stats
import matplotlib.style as style
style.use('classic')
sns.set_style('whitegrid')
# parameters
server = 'desafio-nuvem.database.windows.net'
database = 'desafio-ia'
username = 'markov'
password = 'prypiatRedSky1986'
# create the connection
string_conexao='Driver={SQL Server};Server='+server+';Database='+database+';UID='+username+';PWD='+ password
conn = pyodbc.connect(string_conexao)
sql = """
SELECT *
FROM olist_order_reviews_dataset
"""
reviews = pd.read_sql(sql, conn)
#########################################################
sql = """
SELECT *
FROM olist_orders_dataset
"""
orders = pd.read_sql(sql, conn)
df = reviews.merge(orders, on='order_id', how='outer')
del reviews
del orders
sql = """
SELECT *
FROM olist_order_items_dataset
"""
items = pd.read_sql(sql, conn)
df = df.merge(items, on='order_id', how='left')
del items
# READING DATABASES
############################################################
# query db
sql = """
SELECT *
FROM olist_customers_dataset
"""
customers = pd.read_sql(sql, conn)
df = df.merge(customers, on='customer_id', how='outer')
del customers
###########################################################
sql = """
SELECT *
FROM olist_products_dataset
"""
product = pd.read_sql(sql, conn)
df = df.merge(product, on='product_id', how='outer')
del product
#########################################################
sql = """
SELECT *
FROM olist_order_payments_dataset
"""
payments = pd.read_sql(sql, conn)
df = df.merge(payments, on='order_id', how='outer')
del payments
###########################################################
sql = """
SELECT *
FROM olist_sellers_dataset
"""
seller = pd.read_sql(sql, conn)
df = df.merge(seller, on='seller_id', how='outer')
del seller
def exploraBase(df, pred=None):
obs = df.shape[0]
types = df.dtypes
counts = df.apply(lambda x: x.count())
uniques = df.apply(lambda x: [x.unique()])
nulls = df.apply(lambda x: x.isnull().sum())
distincts = df.apply(lambda x: x.unique().shape[0])
missing_ration = (df.isnull().sum()/ obs) * 100
skewness = df.skew()
kurtosis = df.kurt()
print('Data shape:', df.shape)
if pred is None:
cols = ['types', 'counts', 'distincts', 'nulls', 'missing ration', 'uniques', 'skewness', 'kurtosis']
str = pd.concat([types, counts, distincts, nulls, missing_ration, uniques, skewness, kurtosis], axis = 1)
else:
corr = df.corr()[pred]
str = pd.concat([types, counts, distincts, nulls, missing_ration, uniques, skewness, kurtosis, corr], axis = 1, sort=False)
corr_col = 'corr ' + pred
cols = ['types', 'counts', 'distincts', 'nulls', 'missing_ration', 'uniques', 'skewness', 'kurtosis', corr_col ]
str.columns = cols
dtypes = str.types.value_counts()
print('___________________________\nData types:\n',str.types.value_counts())
print('___________________________')
return str
print("A base de dados contém", df.shape[0], "linhas e", df.shape[1], "colunas.")
## A base de dados contém 119151 linhas e 39 colunas.
def missing_percentage(df):
"""This function takes a DataFrame(df) as input and returns two columns, total missing values and total missing values percentage"""
## the two following line may seem complicated but its actually very simple.
total = df.isnull().sum().sort_values(ascending = False)[df.isnull().sum().sort_values(ascending = False) != 0]
percent = round(df.isnull().sum().sort_values(ascending = False)/len(df)*100,2)[(df.isnull().sum().sort_values(ascending = False)/len(df)*100) != 0]
return pd.concat([total, percent], axis=1, keys=['Total','Percent'])
missing_percentage(df)
## Total Percent
## review_comment_title 104964 88.09
## review_comment_message 67908 56.99
## order_delivered_customer_date 3421 2.87
## product_description_lenght 2542 2.13
## product_category_name 2542 2.13
## product_name_lenght 2542 2.13
## product_photos_qty 2542 2.13
## order_delivered_carrier_date 2086 1.75
## product_weight_g 853 0.72
## product_width_cm 853 0.72
## product_height_cm 853 0.72
## product_length_cm 853 0.72
## seller_state 836 0.70
## seller_zip_code_prefix 836 0.70
## seller_city 836 0.70
## price 833 0.70
## order_item_id 833 0.70
## product_id 833 0.70
## seller_id 833 0.70
## shipping_limit_date 833 0.70
## freight_value 833 0.70
## order_approved_at 177 0.15
## payment_value 3 0.00
## payment_sequential 3 0.00
## payment_type 3 0.00
## payment_installments 3 0.00
review_comment_title e review_comment_message possuem muitos valores ausentes, assim para tratar os valores ausentes será conveniente removêla. Se necessário poderá ser agregada ao datasset posteriormente carregando o dataset review. Após remover estas colunas, observe que a taxa de valores ausentes é pequena, e considerando o tamanho da base de dados, iremos remover as inhas contendo valores ausentes.
df.drop(['review_comment_title','review_comment_message'],axis=1,inplace=True)
df.dropna(inplace=True)
missing_percentage(df)
## Empty DataFrame
## Columns: [Total, Percent]
## Index: []
print("A base de dados contém", df.shape[0], "linhas e", df.shape[1], "colunas.")
## A base de dados contém 114070 linhas e 37 colunas.
detalhes = exploraBase(df)
## Data shape: (114070, 37)
## ___________________________
## Data types:
## object 23
## float64 14
## Name: types, dtype: int64
## ___________________________
detalhes
## types counts ... skewness kurtosis
## review_id object 114070 ... NaN NaN
## order_id object 114070 ... NaN NaN
## review_score float64 114070 ... -1.326437 0.392062
## review_creation_date object 114070 ... NaN NaN
## review_answer_timestamp object 114070 ... NaN NaN
## customer_id object 114070 ... NaN NaN
## order_status object 114070 ... NaN NaN
## order_purchase_timestamp object 114070 ... NaN NaN
## order_approved_at object 114070 ... NaN NaN
## order_delivered_carrier_date object 114070 ... NaN NaN
## order_delivered_customer_date object 114070 ... NaN NaN
## order_estimated_delivery_date object 114070 ... NaN NaN
## order_item_id float64 114070 ... 7.605035 104.447846
## product_id object 114070 ... NaN NaN
## seller_id object 114070 ... NaN NaN
## shipping_limit_date object 114070 ... NaN NaN
## price float64 114070 ... 7.964607 123.044503
## freight_value float64 114070 ... 5.577027 58.940100
## customer_unique_id object 114070 ... NaN NaN
## customer_zip_code_prefix object 114070 ... 0.783798 -0.785096
## customer_city object 114070 ... NaN NaN
## customer_state object 114070 ... NaN NaN
## product_category_name object 114070 ... NaN NaN
## product_name_lenght float64 114070 ... -0.909478 0.157028
## product_description_lenght float64 114070 ... 2.008805 4.926617
## product_photos_qty float64 114070 ... 1.902794 4.756235
## product_weight_g float64 114070 ... 3.586914 16.120781
## product_length_cm float64 114070 ... 1.733799 3.628309
## product_height_cm float64 114070 ... 2.242495 7.284696
## product_width_cm float64 114070 ... 1.705236 4.560173
## payment_sequential float64 114070 ... 15.140699 311.804745
## payment_type object 114070 ... NaN NaN
## payment_installments float64 114070 ... 1.621054 2.534077
## payment_value float64 114070 ... 14.469459 531.907368
## seller_zip_code_prefix object 114070 ... 1.548827 0.908670
## seller_city object 114070 ... NaN NaN
## seller_state object 114070 ... NaN NaN
##
## [37 rows x 8 columns]
Não há mais valores ausentes em nossa base de dados, a seguir será feita uma análise a fim de tratar outliers. É necessário ter atenção em como cada variável foi reconhecida. Data por exemplo, foi lida como objeto, para utilizá-la em uma análise será necessário mudar para o tipo data.
numerical_feats = df.dtypes[df.dtypes != "object"].index
print("Número de variáveis numéricas: ", len(numerical_feats))
## Número de variáveis numéricas: 14
categorical_feats = df.dtypes[df.dtypes == "object"].index
print("Número de variáveis categóricas: ", len(categorical_feats))
## Número de variáveis categóricas: 23
R=df[numerical_feats].corr()
font = {'family' : 'Times New Roman' }
plt.rc('font', **font);
## Plotting heatmap.
fig,ax=plt.subplots(figsize=(20,20))
# Generate a custom diverging colormap
n,c=R.values.shape
num_val_dif=pd.DataFrame(R.values.reshape(n*c,1)).nunique()
cmap = sns.diverging_palette(0,500, n=num_val_dif)
sns.heatmap(R, cmap=cmap, annot=True,annot_kws={"size": 10}, center = 0,square=True,
linewidths=.5, cbar_kws={'shrink': .5,'orientation': 'vertical'},ax=ax);
# Gire os rótulos dos marcadores e defina seu alinhamento.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.figure.axes[-1].yaxis.label.set_size(12)
ax.tick_params(labelsize=12)
ax.figure.subplots_adjust(left = 0.3) # change 0.3 to suit your needs
plt.show()
tratar=list(numerical_feats)
fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="variable", y="value", data=pd.melt(df[tratar].drop('product_weight_g',axis=1)),ax=ax)
# Gire os rótulos dos marcadores e defina seu alinhamento.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()
O gráfico boxplot usa o método IQR para exibir dados e valores discrepantes (formato dos dados), mas para obter uma lista de valores discrepantes identificados, precisamos usar a fórmula matemática e recuperar os dados discrepantes.
Q1 = df[tratar].quantile(0.25)
Q3 = df[tratar].quantile(0.75)
IQR = Q3 - Q1
print(IQR)
## review_score 1.00
## order_item_id 0.00
## price 93.10
## freight_value 8.11
## product_name_lenght 15.00
## product_description_lenght 638.00
## product_photos_qty 2.00
## product_weight_g 1500.00
## product_length_cm 20.00
## product_height_cm 12.00
## product_width_cm 15.00
## payment_sequential 0.00
## payment_installments 3.00
## payment_value 128.42
## dtype: float64
O escore Z é o número sinalizado de desvios padrão pelo qual o valor de uma observação ou ponto de dados está acima do valor médio do que está sendo observado ou medido.
from scipy import stats
import numpy as np
z = np.abs(stats.zscore(df[tratar]))
print(z)
## [[0.04941372 0.2808995 0.35629685 ... 0.13224882 1.81673336 0.84592927]
## [0.04941372 1.14130226 0.35629685 ... 0.13224882 1.81673336 0.84592927]
## [0.6870147 0.2808995 0.35629685 ... 0.13224882 0.01927146 0.12445682]
## ...
## [0.6870147 0.2808995 0.21990905 ... 0.13224882 0.34022092 0.27731315]
## [2.258699 0.2808995 3.45388327 ... 0.13224882 1.81673336 2.23788259]
## [0.6870147 0.2808995 0.22471795 ... 0.13224882 0.01927146 0.10972676]]
z.shape
## (114070, 14)
df_z=pd.DataFrame(z,columns=tratar)
# verificando a presença de outliers em cada variável
for var in tratar:
n_outl=len(df_z[var][df_z[var]>3])
if n_outl!=0:
print('Existem {} outiliers na variável {}'.format(n_outl,var))
## Existem 1880 outiliers na variável order_item_id
## Existem 2029 outiliers na variável price
## Existem 2095 outiliers na variável freight_value
## Existem 561 outiliers na variável product_name_lenght
## Existem 2780 outiliers na variável product_description_lenght
## Existem 1612 outiliers na variável product_photos_qty
## Existem 2935 outiliers na variável product_weight_g
## Existem 2221 outiliers na variável product_length_cm
## Existem 2770 outiliers na variável product_height_cm
## Existem 1749 outiliers na variável product_width_cm
## Existem 975 outiliers na variável payment_sequential
## Existem 400 outiliers na variável payment_installments
## Existem 1755 outiliers na variável payment_value
del df_z
sns.distplot(df.price,kde=False)
Agora podemos remover ou filtrar os outliers e obter os dados limpos. Isso pode ser feito com apenas um código de linha, pois já calculamos o Z-score.
df=df[(z < 3).all(axis=1)]
sns.distplot(df.price,kde=False)
Assim como o Z-score, podemos usar o IQR calculado anteriormente para filtrar os valores extremos
#df = df[~((df < (Q1 - 1.5 * IQR)) |(df > (Q3 + 1.5 * IQR))).any(axis=1)]
Agora que os outliers foram removidos podemos começar a buscar alguns insights e ajustar um modelo para predição.
print("A base de dados contém", df.shape[0], "linhas e", df.shape[1], "colunas.")
## A base de dados contém 97423 linhas e 37 colunas.
Quais estados concentram o maior número de vendas?
# nunique: Contar observações distintas sobre o eixo solicitado.
n_estados = df["customer_state"].nunique()
df2 = df.groupby(['customer_state'])[['customer_id']].nunique().sort_values('customer_id',ascending=False)
df2=df2.head(10)
print("São ",n_estados,"estados na base de dados. Os 10 estados com maior número de clientes são:")
## São 27 estados na base de dados. Os 10 estados com maior número de clientes são:
df2.reset_index(level=0, inplace=True)
df2.columns=['Estado','N_Clientes']
df2
## Estado N_Clientes
## 0 SP 35270
## 1 RJ 10679
## 2 MG 9924
## 3 RS 4629
## 4 PR 4278
## 5 SC 3042
## 6 BA 2803
## 7 DF 1823
## 8 ES 1753
## 9 GO 1687
Para uma melhor visualização dos resultados veja o gráfico a seguir:
import numpy as np
pal = sns.color_palette("Blues_d", len(df2))
rank = df2.N_Clientes.argsort().argsort()
fig,ax=plt.subplots(figsize=(8,5))
g=sns.barplot(x='Estado',y='N_Clientes',data=df2,
palette=np.array(pal[::-1])[rank],
ci = None,ax=ax)
for p in g.patches:
g.annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha = 'center', va = 'center', xytext = (0, 10), textcoords = 'offset points')
# Gire os rótulos dos marcadores e defina seu alinhamento.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel('Número de Clientes')
plt.tight_layout();
plt.show()
O número de cidades é muito grande, e até mesmo o de estados para plotar todas estas informações em um gráfico de barras ou tabela contendo o total de clientes por estado por exemplo. Uma forma de exibir estes resultados de forma sintetizada, é utilizar as informações de geolocalização, e plotar como um pequeno ponto, cada cliente em um maapa. Isso nos dará uma ótima vizualização de como os clientes estão distribuídos ao longo do país.
# Obter os valores de latitude e longitude de cada cliente para plotar
import folium
map = folium.Map(location=[-15.7797200,-47.9297200], zoom_start=4,tiles='openstreetmap')
map
## <folium.folium.Map object at 0x000000000D3E3E08>
Quais são as categorias de produtos mais vendidas?
df2=df.copy()
df2['Pontuacao']=1
df2 = df2.groupby(['product_category_name'])[['Pontuacao']].sum().sort_values('Pontuacao',ascending=False)
n_categorias = df['product_category_name'].nunique()
print("São ",n_categorias,"categorias na base de dados. As 10 categorias mais vendidas são:")
## São 73 categorias na base de dados. As 10 categorias mais vendidas são:
df2.reset_index(level=0, inplace=True)
df2.columns=['product_category_name','N_Vendas']
df2.head(10)
## product_category_name N_Vendas
## 0 cama_mesa_banho 11032
## 1 beleza_saude 8597
## 2 informatica_acessorios 7326
## 3 esporte_lazer 7254
## 4 moveis_decoracao 7004
## 5 utilidades_domesticas 5783
## 6 relogios_presentes 5313
## 7 telefonia 4335
## 8 ferramentas_jardim 3969
## 9 brinquedos 3691
pal = sns.color_palette("Greens_d", len(df2))
rank = df2.N_Vendas.argsort().argsort()
fig,ax=plt.subplots(figsize=(8,5))
g=sns.barplot(x='product_category_name',y='N_Vendas',data=df2,
palette=np.array(pal[::-1])[rank],
ci = None,ax=ax)
# Gire os rótulos dos marcadores e defina seu alinhamento.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel('Número de itens Vendidos')
ax.get_xaxis().set_visible(False)
plt.tight_layout();
plt.show()
n=10
pal = sns.color_palette("Blues_d", len(df2.head(n)))
rank = df2.head(n).N_Vendas.argsort().argsort()
fig,ax=plt.subplots(figsize=(8,5))
g=sns.barplot(x='product_category_name',y='N_Vendas',data=df2.head(n),
palette=np.array(pal[::-1])[rank],
ci = None,ax=ax)
for p in g.patches:
g.annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha = 'center', va = 'center', xytext = (0, 10), textcoords = 'offset points')
# Gire os rótulos dos marcadores e defina seu alinhamento.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel('Número de itens Vendidos')
plt.tight_layout();
plt.show()
Satisfação dos clientes: o review dos clientes tem sido favorável?
df2=df.copy()
df2['Pontuacao']=1
df2 = df2.groupby(['review_score'])[['Pontuacao']].sum().sort_values('Pontuacao',ascending=False)
df2.reset_index(level=0, inplace=True)
df2.columns=['Review_Score','N_Avaliacoes']
df2
## Review_Score N_Avaliacoes
## 0 5.0 56225
## 1 4.0 18818
## 2 1.0 10837
## 3 3.0 8225
## 4 2.0 3318
pal = sns.hls_palette(5, l=.3, s=.8)
rank = df2.N_Avaliacoes.argsort().argsort()
fig,ax=plt.subplots(figsize=(8,5))
g=sns.barplot(x='Review_Score',y='N_Avaliacoes',data=df2,
palette=np.array(pal[::-1])[rank],
ci = None,ax=ax)
for p in g.patches:
g.annotate(format(p.get_height(), '.0f'), (p.get_x() + p.get_width() / 2., p.get_height()), ha = 'center', va = 'center', xytext = (0, 10), textcoords = 'offset points')
# Gire os rótulos dos marcadores e defina seu alinhamento.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
ax.set_ylabel(' ')
plt.tight_layout();
plt.show()
Número de vendas e reviews ao longo do tempo.
Para iniciar, é necessário transformar a coluna com as datas em datetime.
col_datas=['review_creation_date','order_purchase_timestamp','order_delivered_carrier_date','order_delivered_customer_date',
'order_estimated_delivery_date','order_approved_at','shipping_limit_date'
]
df[col_datas]=df[col_datas].apply(pd.to_datetime)
df['Data'] = df['review_creation_date'].apply(lambda x: x.strftime('%Y-%m'))
df['Data']=df['Data'].apply(pd.to_datetime)
df2=df.copy()
# df2.dropna(inplace=True)
df2['Pontuacao']=1
df2=df2.groupby(['review_score','Data'])[['Pontuacao']].sum()
df2.reset_index(level=0, inplace=True)
df2.reset_index(level=0, inplace=True)
keys=[i for i in range(1,6)]
values=['Nota {}'.format(i) for i in range(1,6)]
df2['review_score'] = df2['review_score'].map(dict(zip(keys, values)))
df2=df2[(df2['Data'] > '2014-01-01') & (df2['Data'] < '2018-08-01')]
# style.use('default')
fig,ax=plt.subplots(figsize=(8,5))
# adiciona um título e os rótulos dos eixos
## Plot
sns.lineplot(x="Data", y='Pontuacao',hue='review_score',data=df2,ax=ax)
sns.scatterplot(x="Data", y='Pontuacao',hue='review_score',style='review_score',
data=df2,ax=ax,legend=False,clip_on=False);
ax.set_xlabel('Data de criação do review')
ax.set_ylabel('Número de Avaliações')
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
l = ["upper left", "lower left", "center right"];
ax.legend(loc=l[0], bbox_to_anchor=(1,1));
plt.ylim(0);
plt.tight_layout();
plt.show ()
df['Data'] = df['order_approved_at'].apply(lambda x: x.strftime('%Y-%m'))
df['Data']=df['Data'].apply(pd.to_datetime)
df2=df.copy()
df2['Pontuacao']=1
df2=df2.groupby(['Data'])[['Pontuacao']].sum()
df2.reset_index(level=0, inplace=True)
df2=df2[(df2['Data'] > '2014-01-01') & (df2['Data'] < '2018-07-01')]
fig,ax=plt.subplots(figsize=(8,5))
# adiciona um título e os rótulos dos eixos
### Plot
ax.plot(df2['Data'], df2['Pontuacao'],'o-',color='green',clip_on=False)
ax.set_xlabel('')
ax.set_ylabel('Número de Itens Vendidos')
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show ()
from statistics import median
mean = df["payment_value"].mean()
mediana = df["payment_value"].median()
std = df["payment_value"].std()
text1 = '$\mu=$' + str(round(mean,2))
text2 = '$mediana=$' + str(round(mediana,2))
text3 = '$\sigma=$' +str(round(std,2))
text = text1 + "\n" + text2 + "\n" + text3
fig,ax=plt.subplots(figsize=(10,7))
df.hist(column = "payment_value", bins = 100,color='#2F0B92', figsize=(15,8), rwidth=0.9,ax=ax);
plt.axvline(mediana, color='r', linestyle='--')
ax.text(mediana+10, 6000, text, fontsize=12)
ax.set_xlabel("Valor do pagamento")
ax.set_ylabel("Counts")
ax.set_title("Histograma dos valores de pagamentos")
plt.tight_layout();
plt.show()
df2=df.sort_values('Data', ascending=True)
df2=df2.groupby(['Data'])[['payment_value']].sum()
df2['cum_sum'] = df2['payment_value'].cumsum()
df2.reset_index(level=0, inplace=True)
df2=df2[(df2['Data'] > '2014-01-01') & (df2['Data'] < '2018-08-01')]
df2['cum_sum']=df2['cum_sum']/1000000
fig,ax=plt.subplots(figsize=(8,5))
## Plot
plt.plot(df2['Data'], df2['cum_sum'],'o-',clip_on=False)
ax.fill_between(df2['Data'], df2['cum_sum'], 0,color='#b3b3ff')
ax.set_xlabel(' ')
ax.set_ylabel('Soma acumulada de valor de pagamento (Milhões)')
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show ()
Além de produtos de boa qualidade para garantir a satisfação dos clientes é essencial que o processo de compra até a entrega seja ágil. Portanto o tempo gasto entre a data da compra e a entrega é uma variável muito importante que deve ser considerada para predizer a satisfação. O preço dos produtos também é excencial, afinal os clientes ficam satisfeitos quando julgam o valor pago pelo produto justo ou vantajoso.
A seguir listamos as variáveis disponíveis no dataset que contém características do produto, data da compra e entrega, valor do frete.
order_purchase_timestamp até a entrega ao cliente order_delivered_customer_date.order_purchase_timestamp até a data prevista de entrega order_estimated_delivery_date.freight_value.product_photos_qty.df['Tempo_entrega']=df.order_delivered_customer_date-df.order_purchase_timestamp
df['Tempo_entrega']=df['Tempo_entrega'].dt.days
df['Tempo_entrega_previsto']=df.order_estimated_delivery_date-df.order_purchase_timestamp
df['Tempo_entrega_previsto']=df['Tempo_entrega_previsto'].dt.days
df.iloc[:,-2:].head()
## Tempo_entrega Tempo_entrega_previsto
## 0 6 21
## 1 6 21
## 2 14 31
## 3 19 31
## 4 17 23
fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="review_score", y="Tempo_entrega", data=df,ax=ax)
# sns.stripplot(x="review_score", y="Tempo_entrega", data=df,ax=ax)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()
Como pode ser observado nos boxplots acima, o tempo de entrega afeta o review. Apesar de terem a mediana próxima dos produtores com maior review, eles apresentam maior variação no tempo de entrega e maior probabilidade de atraso na entrega dos produtos.
fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="review_score", y="freight_value", data=df,ax=ax)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()
fig, ax = plt.subplots(figsize=(9, 8), subplot_kw=dict(aspect="equal"))
explode = (0.1, 0, 0, 0)
colors = ['#f45a5a', '#449dfc', '#93f96d', '#f9c86d']
legend = ["Credit Card", "Boleto", "Voucher", "Debit Card"]
p = df["payment_type"][df["payment_type"] != "not_defined"].value_counts()
p.plot(kind="pie", legend=False, labels=None, startangle=0, explode=explode, autopct='%1.0f%%', pctdistance=0.6, shadow=True, textprops={'weight':'bold', 'fontsize':16},
colors=colors, ax=ax)
ax.legend(legend, loc='best', shadow=True, prop={'weight':'bold', 'size':12}, bbox_to_anchor=(0.6, 0, 0.5,1))
plt.title("Métodos de pagamento", fontweight='bold', size=16)
plt.tight_layout();
plt.ylabel("");
plt.show()
# Imports
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from scipy.spatial.distance import cdist, pdist
import warnings
Iremos agrupar os items vendidos baseado em suas características a fim de verificar quais categorias se encontram em cada cluster. Com este agrupamento, poderemos observar também se as características do produto afeta o review do cliente. Para isto iremos utilizar o algoritmo de k-means. Este processo agrupará os produtos minimizando a similaridade dentro dos clusters e maximizando a similaridae entre produtos de clusters distintos.
df2=df[['product_id','product_category_name', 'product_name_lenght',
'product_description_lenght', 'product_photos_qty', 'product_weight_g',
'product_length_cm', 'product_height_cm', 'product_width_cm']]
df2=df2.groupby(['product_id','product_category_name']).mean()
Encontrar o melhor número de clusters para esse conjunto de dados
Para o método de agrupamento k-means, a abordagem mais comum para responder a essa pergunta é o chamado método de elbow (cotovelo). Isso envolve executar o algoritmo várias vezes em um loop, com um número crescente de opções de cluster e, em seguida, plotar uma pontuação de cluster como uma função do número de clusters.
A pontuação é, em geral, alguma forma de distância entre clusters em relação à distância interna do cluster .
# Aplica redução de dimensionalidade
pca = PCA(n_components = 2).fit_transform(df2)
pca=pd.DataFrame(pca,columns=['Componente 1','Componente 2'])
# Determinando um range de K
k_range = range(1,12)
# Aplicando o modelo K-Means para cada valor de K
k_means_var = [KMeans(n_clusters = k).fit(pca) for k in k_range]
# Ajustando o centróide do cluster para cada modelo
centroids = [X.cluster_centers_ for X in k_means_var]
# Calculando a distância euclidiana de cada ponto de dado para o centróide
k_euclid = [cdist(pca, cent, 'euclidean') for cent in centroids]
dist = [np.min(ke, axis = 1) for ke in k_euclid]
# Soma dos quadrados das distâncias dentro do cluster
soma_quadrados_intra_cluster = [sum(d**2) for d in dist]
# Soma total dos quadrados
soma_total = sum(pdist(pca)**2)/pca.shape[0]
# Soma dos quadrados entre clusters
soma_quadrados_inter_cluster = soma_total - soma_quadrados_intra_cluster
# Curva de Elbow
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(k_range, soma_quadrados_inter_cluster/soma_total * 100, 'b*-')
ax.set_ylim((0,100));
plt.grid(True);
plt.xlabel('Número de Clusters');
plt.ylabel('Percentual de Variância Explicada');
plt.title('Variância Explicada x Valor de K');
plt.tight_layout();
plt.show()
# Criando um modelo com K = 5
modelo_v1 = KMeans(n_clusters = 5)
modelo_v1.fit(pca)
## KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
## n_clusters=5, n_init=10, n_jobs=None, precompute_distances='auto',
## random_state=None, tol=0.0001, verbose=0)
fig,ax=plt.subplots(figsize=(8,5))
# Plot dos centróides
#Plot dos Clusters
colors = ["black", "green", "blue","yellow","orange"]
pca2=pca
pca2['cluster'] = modelo_v1.labels_
pca2['color'] = pca['cluster'].map(lambda p: colors[p])
pca2.plot(kind="scatter", x="Componente 1", y="Componente 2",c = pca2['color'],alpha=0.8,s=100,ax=ax)
centroids = modelo_v1.cluster_centers_
inert = modelo_v1.inertia_
plt.scatter(centroids[:, 0], centroids[:, 1], marker = 'x', s = 169, linewidths = 3, color = 'r', zorder = 8)
plt.xticks(());
plt.yticks(());
plt.xlabel('Componente 1');
plt.ylabel('Componente 2');
plt.tight_layout();
plt.show()
df2['cluster'] = modelo_v1.labels_
df2['review_score']=df.groupby(['product_id','product_category_name'])['review_score'].mean()
fig,ax=plt.subplots(figsize=(8,5))
sns.boxplot(x="cluster", y="review_score", data=df2,ax=ax)
plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor");
plt.tight_layout();
plt.show()
Nesta etapa ajustaremos um modelo a fim de predizer o review do cliente baseado em algumas informações. Para isso usaremos a técnica de classificação random forest.
Inicialmente o conjunto de dados será dividido em conjunto de trinamento e conjunto de testes. 90% dos clientes serão incluidos no modelo de treinamento, e 10% serão adicionados ao dataset de teste. Desta forma após ajustar o molelo, será possível verificar seu desempenho quando utilizamos um atributos de entrada pertencentes a indivíduos fora do conjunto de treino.
### Modelo
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
X=df[['customer_id', 'price', 'freight_value', 'product_name_lenght','product_description_lenght',
'product_photos_qty', 'product_weight_g','product_length_cm', 'product_height_cm',
'product_width_cm', 'payment_value', 'Tempo_entrega', 'Tempo_entrega_previsto']]
X.set_index('customer_id',inplace=True);
y=df.review_score
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.10)
X_train.shape, X_test.shape
## ((87680, 12), (9743, 12))
# Random Forest
rf_classifier = RandomForestClassifier(n_estimators = 10, criterion = 'entropy', random_state = 0)
rf_classifier.fit(X_train, y_train)
## RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
## criterion='entropy', max_depth=None, max_features='auto',
## max_leaf_nodes=None, max_samples=None,
## min_impurity_decrease=0.0, min_impurity_split=None,
## min_samples_leaf=1, min_samples_split=2,
## min_weight_fraction_leaf=0.0, n_estimators=10,
## n_jobs=None, oob_score=False, random_state=0, verbose=0,
## warm_start=False)
y_pred_rf = rf_classifier.predict(X_test)
Para avaliar o deempenho do modelo iremos utilizar uma matriz de confusão.
#confusion_matrix
cnf_matrix = confusion_matrix(y_test,y_pred_rf)
p = sns.heatmap(pd.DataFrame(cnf_matrix), annot=True, cmap="YlGnBu" ,
fmt='g',yticklabels=[1,2,3,4,5],xticklabels=[1,2,3,4,5])
plt.title('Confusion matrix', y=1.1);
plt.ylabel('True label');
plt.xlabel('Predicted label');
plt.show()
p=sns.heatmap(cnf_matrix/np.sum(cnf_matrix), annot=True, fmt='.2%', cmap="YlGnBu",
yticklabels=[1,2,3,4,5],xticklabels=[1,2,3,4,5])
plt.title('Confusion matrix', y=1.1);
plt.ylabel('True label');
plt.xlabel('Predicted label');
plt.show()
Trabalho de Cristiano Ferreira de Oliveira
cristiano2132.ufv@gmail.com